#!/usr/bin/python3

## Copyright (C) 2014 ABRT team <abrt-devel-list@redhat.com>
## Copyright (C) 2014 Red Hat, Inc.

## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.

## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.

## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA

"""This module provides algorithms for generating Machine IDs.
"""

import os
import sys
from argparse import ArgumentParser
from subprocess import check_output, CalledProcessError
import logging

import hashlib

def generate_machine_id_dmidecode():
    """Generate a machine_id based off dmidecode fields

    The function generates the same result as sosreport-uploader

    Returns a machine ID as string or throws RuntimeException

    """

    if not os.path.isfile("/usr/sbin/dmidecode"):
        raise RuntimeError("Could not find dmidecode. It might not be available for this " \
                           "architecture.")

    # What to look for
    keys = ['system-manufacturer',
            'system-product-name',
            'system-serial-number',
            'system-uuid']

    # Create a sha256 of ^ for machine_id
    machine_id = hashlib.sha256()

    # Run dmidecode command
    for k in keys:
        try:
            data = check_output(["dmidecode", "-s", k]).strip()
        except (OSError, CalledProcessError) as ex:
            raise RuntimeError("Execution of dmidecode failed: {0}".format(str(ex)))

        # Update the hash as we find the fields we are looking for
        machine_id.update(data)

    # Create sha256 digest
    return machine_id.hexdigest()


def generate_machine_id_systemd():
    """Generate a machine_id equals to a one generated by systemd

    This function returns contents of /etc/machine-id

    Returns a machine ID as string or throws RuntimeException.

    """

    try:
        with open('/etc/machine-id', 'r') as midf:
            return "".join((l.strip() for l in midf))
    except IOError as ex:
        raise RuntimeError("Could not use systemd's machine-id: {0}"
                .format(str(ex)))


GENERATORS = { 'sosreport_uploader-dmidecode' : generate_machine_id_dmidecode,
               'systemd'                      : generate_machine_id_systemd }


def generate_machine_id(generators):
    """Generates all requested machine id with all required generators

    Keyword arguments:
    generators -- a list of generator names

    Returns a dictionary where keys are generators and associated values are
    products of those generators.

    """

    ids = {}
    workers = GENERATORS
    for sd in generators:
        try:
            ids[sd] = workers[sd]()
        except RuntimeError as ex:
            logging.error("Machine-ID generator '{0}' failed: {1}"
                        .format(sd, str(ex)))

    return ids


def print_result(ids, outfile, prefixed):
    """Writes a dictionary of machine ids to a file

    Each dictionary entry is written on a single line. The function does not
    print trailing new-line if the dictionary contains only one item as it is
    common format of one-liners placed in a dump directory.

    Keyword arguments:
    ids -- a dictionary [generator name: machine ids]
    outfile -- output file
    prefixed -- use 'generator name=' prefix or not
    """

    fmt = '{0}={1}' if prefixed else '{1}'

    if len(ids) > 1:
        fmt += '\n'

    for sd, mid in ids.items():
        outfile.write(fmt.format(sd,mid))


def print_generators(outfile=None):
    """Prints requested generators

    Keyword arguments:
    outfile -- output file (default: sys.stdout)

    """
    if outfile is None:
        outfile = sys.stdout

    for sd in GENERATORS.keys():
        outfile.write("{0}\n".format(sd))


if __name__ == '__main__':
    CMDARGS = ArgumentParser(description = "Generate a machine_id")
    CMDARGS.add_argument('-o', '--output', type=str,
            help="Output file")
    CMDARGS.add_argument('-g', '--generators', nargs='+', type=str,
            help="Use given generators only")
    CMDARGS.add_argument('-l', '--list-generators', action='store_true',
            default=False, help="Print out a list of usable generators")
    CMDARGS.add_argument('-n', '--noprefix', action='store_true',
            default=False, help="Do not use generator name as prefix for IDs")

    OPTIONS = CMDARGS.parse_args()
    ARGS = vars(OPTIONS)

    logging.basicConfig(format='%(message)s')

    if ARGS['list_generators']:
        print_generators()
        sys.exit(0)

    requested_generators = None
    if ARGS['generators']:
        requested_generators = ARGS['generators']
    else:
        requested_generators = list(GENERATORS.keys())

    machineids = generate_machine_id(requested_generators)

    if ARGS['output']:
        try:
            with open(ARGS['output'], 'w') as fout:
                print_result(machineids, fout, not ARGS['noprefix'])
        except IOError as ex:
            logging.error("Could not open output file: {0}".format(str(ex)))
            sys.exit(1)
    else:
        print_result(machineids, sys.stdout, not ARGS['noprefix'])
        # print_results() omits new-line for one-liners
        if len(machineids) == 1:
            sys.stdout.write('\n')

    sys.exit(len(requested_generators) - len(machineids))
